package x.ovo.wechat.bot.impl.http;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.thread.ThreadUtil;
import org.dromara.hutool.core.util.RandomUtil;
import org.dromara.hutool.json.JSONUtil;
import x.ovo.wechat.bot.core.Context;
import x.ovo.wechat.bot.core.entity.*;
import x.ovo.wechat.bot.core.enums.LoginMode;
import x.ovo.wechat.bot.core.enums.MessageType;
import x.ovo.wechat.bot.core.event.LoginSystemEvent;
import x.ovo.wechat.bot.core.exception.LoginException;
import x.ovo.wechat.bot.core.http.WechatApi;
import x.ovo.wechat.bot.core.http.request.BaseRequest;
import x.ovo.wechat.bot.core.http.result.SyncCheckResult;
import x.ovo.wechat.bot.core.http.result.UploadResult;
import x.ovo.wechat.bot.core.http.result.WebSyncResult;
import x.ovo.wechat.bot.core.http.session.Session;
import x.ovo.wechat.bot.core.util.WechatUtil;
import x.ovo.wechat.bot.impl.config.ClientConfig;
import x.ovo.wechat.bot.impl.core.HotReload;
import x.ovo.wechat.bot.impl.http.request.contact.*;
import x.ovo.wechat.bot.impl.http.request.core.*;
import x.ovo.wechat.bot.impl.http.request.message.*;
import x.ovo.wechat.bot.impl.http.result.CheckLoginResult;
import x.ovo.wechat.bot.impl.http.result.InitWechatResult;

import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j(topic = "WechatApi")
public class DefaultWechatApi implements WechatApi {

    @Getter
    private Session session;
    private final Context ctx = Context.INSTANCE;
    private final ClientConfig config = ClientConfig.get();

    @Override
    public String getUUID() {
        return new UUIDRequest().execute();
    }

    @Override
    public void printQrCode(String uuid) {
        new QrCodeRequest(uuid).execute();
    }

    @Override
    public boolean login(boolean autoLogin) {
        if (this.ctx.isLogedin()) {
            log.warn("已经登录，无需重复登录");
            return true;
        }

        // 如果为自动登录，且自动登录成功则返回true
        if (autoLogin && this.autoLogin()) return true;

        int count = 0;
        while (!this.ctx.isLogedin() && count < this.config.getReteyCount()) {
            count++;
            AtomicInteger tip = new AtomicInteger(1);
            String uuid = Optional.ofNullable(this.pushLogin()).orElseGet(() -> {
                tip.set(0);
                String s = this.getUUID();
                this.printQrCode(s);
                return s;
            });
            boolean b = this.checkLogin(uuid, tip.get());
            log.debug("第 {} 次登录，登录结果: {}", count, b);
            if (b) return true;
        }
        throw new LoginException("登录失败，且达到最大重试次数");
    }

    @Override
    public String pushLogin() {
        if (Objects.isNull(this.session) || StrUtil.isBlank(this.session.getWxUin())) return null;
        return new PushLoginRequest().execute();
    }

    @Override
    public boolean autoLogin() {
        try {
            // 如果session为空，则从保存到的登录信息中获取
            if (Objects.isNull(this.session)) {
                HotReload reload = HotReload.load();
                this.session = reload.getSession();
                this.ctx.setSession(this.session);
                DefaultCookieStore.setCookies(reload.getCookies());
            }
            // 通过获取自己的用户信息检测session是否有效
            if (Objects.nonNull(this.getContact(this.session.getUserName()))) {
                this.ctx.setLogedin(true);
                this.ctx.setAutoLogedin(true);
                new LoginSystemEvent(this.session.getNickName() + " 自动登录").fire();
                log.info("自动登录成功, Uin: {}, Name: {}", this.session.getWxUin(), this.session.getNickName());
                return true;
            }
        } catch (Exception e) {
            log.error("自动登录失败: {}", e.getMessage());
            HotReload.delete();
        }
        return false;
    }

    @Override
    public boolean checkLogin(String uuid, int tip) {
        while (true) {
            ThreadUtil.safeSleep(TimeUnit.SECONDS.toMillis(2));
            // 检测登录状态
            CheckLoginResult result = new CheckLoginRequest(tip, uuid).execute();
            log.info("{}", result.getLoginMode().getMessage());
            if (LoginMode.FAILURE.equals(result.getLoginMode())) return false;
            // 登录成功，创建session，并获取相关信息填充
            if (LoginMode.SUCCESS.equals(result.getLoginMode())) {
                this.session = new Session();
                // 获取基础请求，拿到key及ticket
                BaseRequest baseRequest = new SessionRequest(result).execute();
                this.session.init(baseRequest, DefaultCookieStore.getCookies());
                log.debug("BaseRequest: {}", JSONUtil.toJsonStr(baseRequest));
                this.ctx.setLogedin(true);
                this.ctx.setSession(this.session);
                return true;
            }
        }
    }

    @Override
    public boolean logout() {
        if (this.ctx.isLogedin()) {
            this.ctx.setLogedin(false);
            new LogoutRequest().execute();
        }
        return this.ctx.isLogedin();
    }

    @Override
    public void init() {
        // 如果自动登录成功则返回，不发起初始化请求，否则syncKey为扫码登录时的key，会重复拉取消费消息
        if (this.ctx.isLogedin() && this.ctx.isAutoLogedin()) return;

        // 初始化微信，获取登陆后的初始化信息，如联系人列表、同步密钥等
        InitWechatResult result = new InitWechatRequest().execute();
        if (result.failure()) throw new LoginException("初始化失败: " + result.getBaseResponse());

        // 最近联系人列表
        List<Contact> contactList = result.getContactList();
        this.ctx.getContactManager().syncRecent(contactList);

        Contact user = result.getUser();
        this.session.setContact(user);
        this.session.setUserName(user.getUserName());
        this.session.setNickName(WechatUtil.format(user.getNickName()));
        this.session.setSyncKey(result.getSyncKey());
        this.session.setSyncCheckKey(result.getSyncKey());
        this.session.setInviteStartCount(result.getInviteStartCount());

        new LoginSystemEvent(this.session.getNickName()).fire();
    }

    @Override
    public boolean statusNotify(String id) {
        return new StatusNotifyRequest(id).execute();
    }

    @Override
    public SyncCheckResult syncCheck() {
        return new SyncCheckRequest().execute();
    }

    @Override
    public WebSyncResult webSync() {
        return new WebSyncRequest().execute();
    }

    @Override
    public boolean sendText(String to, String content) {
        String msgId = System.currentTimeMillis() / 1000 + RandomUtil.randomString(6);
        SendMessage message = SendMessage.builder()
                .Content(content)
                .FromUserName(this.session.getUserName())
                .ToUserName(to)
                .LocalID(msgId)
                .ClientMsgId(msgId)
                .Type(MessageType.TEXT.getCode())
                .build();
        Boolean execute = new SendTextRequest(message).execute();
        log.info("{} -> {}: [文本消息::{}] {}", this.session.getNickName(), this.ctx.getContactManager().get(to).getNickName(), execute ? "成功" : "失败", content);
        return execute;
    }

    @Override
    public boolean sendEmoticon(String to, String emoticonMd5) {
        String id = System.currentTimeMillis() / 1000 + RandomUtil.randomString(6);
        SendMessage message = SendMessage.builder()
                .EMoticonMd5(emoticonMd5)
                .FromUserName(this.session.getUserName())
                .ToUserName(to)
                .LocalID(id)
                .ClientMsgId(id)
                .Type(MessageType.EMOTICON.getCode())
                .build();
        Boolean execute = new SendEmoticonRequest(message).execute();
        log.info("{} -> {}: [表情消息::{}] {}", this.session.getNickName(), this.ctx.getContactManager().get(to).getNickName(), execute ? "成功" : "失败", emoticonMd5);
        return execute;
    }

    @Override
    public boolean sendImage(String to, File file) {
        UploadResult result = this.uploadMedia(to, file);
        String msgId = System.currentTimeMillis() + RandomUtil.randomString("1234567890", 4);
        SendMessage message = SendMessage.builder()
                .Type(MessageType.IMAGE.getCode())
                .FromUserName(this.session.getUserName())
                .ToUserName(to)
                .LocalID(msgId)
                .ClientMsgId(msgId)
                .Content("")
                .MediaId(result.getMediaId())
                .build();
        Boolean execute = new SendImageRequest(message).execute();
        log.info("{} -> {}: [图片消息::{}] {}", this.session.getNickName(), this.ctx.getContactManager().get(to).getNickName(), execute ? "成功" : "失败", file.getName());
        return execute;
    }

    @Override
    public boolean sendVoice(String to, File file) {
        return false;
    }

    @Override
    public boolean sendVideo(String to, File file) {
        UploadResult result = this.uploadMedia(to, file);
        String msgId = System.currentTimeMillis() + RandomUtil.randomString("1234567890", 4);
        SendMessage message = SendMessage.builder()
                .Type(MessageType.VIDEO.getCode())
                .FromUserName(this.session.getUserName())
                .ToUserName(to)
                .LocalID(msgId)
                .ClientMsgId(msgId)
                .Content("")
                .MediaId(result.getMediaId())
                .build();
        Boolean execute = new SendVideoRequest(message).execute();
        log.info("{} -> {}: [视频消息::{}] {}", this.session.getNickName(), this.ctx.getContactManager().get(to).getNickName(), execute ? "成功" : "失败", file.getName());
        return execute;
    }

    @Override
    public boolean sendFile(String to, File file) {
        UploadResult result = this.uploadMedia(to, file);
        String title = FileUtil.getName(file.toPath());
        String type = FileUtil.getType(file);
        String msgId = System.currentTimeMillis() + RandomUtil.randomString("1234567890", 4);
        String content = StrUtil.format("<appmsg appid='wxeb7ec651dd0aefa9' sdkver=''>" +
                        "<title>{}</title><des></des><action></action><type>6</type><content></content><url></url><lowurl></lowurl>" +
                        "<appattach><totallen>{}</totallen><attachid>{}</attachid><fileext>{}</fileext></appattach><extinfo></extinfo></appmsg>",
                title, result.getStartPos(), result.getMediaId(), type);
        SendMessage message = SendMessage.builder()
                .Type(MessageType.FILE.getCode())
                .FromUserName(this.session.getUserName())
                .ToUserName(to)
                .LocalID(msgId)
                .ClientMsgId(msgId)
                .Content(content)
                .build();
        Boolean execute = new SendFileRequest(message).execute();
        log.info("{} -> {}: [文件消息::{}] {}", this.session.getNickName(), this.ctx.getContactManager().get(to).getNickName(), execute ? "成功" : "失败", file.getName());
        return execute;
    }

    @Override
    public UploadResult uploadMedia(String to, File file) {
        Assert.isTrue(FileUtil.isFile(file) && FileUtil.size(file) > 0, "文件不存在或大小为0");
        log.debug("上传文件：{}", file.getName());
        return new MediaUploadRequest(file, to).execute();
    }

    @Override
    public byte[] getImage(Long msgId) {
        return new GetImageRequest(msgId).execute();
    }

    @Override
    public byte[] getVoice(Long msgId) {
        return new GetVoiceRequest(msgId).execute();
    }

    @Override
    public byte[] getVideo(Long msgId) {
        return new GetVideoRequest(msgId).execute();
    }

    @Override
    public List<Contact> listContact() {
        List<Contact> list = new ListContactRequest().execute();
        list.forEach(Contact::format);
        return list;
    }

    @Override
    public List<Contact> listContact(Collection<BatchContactQuery> queries) {
        List<Contact> list = new BatchContactRequest(queries).execute();
        list.forEach(Contact::format);
        list.stream().flatMap(c -> c.getMemberList().stream()).forEach(Member::format);
        return list;
    }

    @Override
    public Contact getContact(String username) {
        BatchContactQuery query = BatchContactQuery.builder().EncryChatRoomId("").UserName(username).build();
        return this.listContact(List.of(query)).get(0);
    }

    @Override
    public boolean verifyUser(Recommend recommend) {
        return new VerifyRequest(recommend).execute();
    }

    @Override
    public boolean addFriend(String username, String content) {
        return new AddFriendRequest(username, content).execute();
    }

    @Override
    public boolean modifyRemark(String username, String remark) {
        return new ModifyRemarkRequest(username, remark).execute();
    }

    @Override
    public boolean creatGroup(String groupName, Collection<String> members) {
        return new CreateGroupRequest(groupName, members).execute();
    }

    @Override
    public boolean removeMember(String groupName, String member) {
        return new RemoveMemberRequest(groupName, member).execute();
    }

    @Override
    public boolean inviteMember(String groupName, String member) {
        return new InviteMemberRequest(groupName, member).execute();
    }

    @Override
    public boolean modifyGroupName(String groupName, String newName) {
        return new ModifyGroupNameRequest(groupName, newName).execute();
    }

    @Override
    public List<Member> listMember(String groupName) {
        List<Member> list = this.getContact(groupName).getMemberList();
        list.forEach(Member::format);
        return list;
    }
}
